'use restrict';
const elasticsearch = require('elasticsearch');
const cfg = require('../config');

const logger = require('koa-logger');
let fs = require('fs');
const log = require('../logUtil');

let getclient = function () {
  return elasticsearch.Client({
    hosts: [cfg.host],
    maxRetries: 5,
    requestTimeout: 880000,
  });
};
function getStore() {
  delete require.cache[require.resolve('../store')]
  return require('../store')
}
let nestedProperty = [
  'Diagnosis',
  'FullText',
  'VAllergyDiagnosis',
  'VClinicDiagnosis',
  'VContact',
  'VICU',
  'VOperate',
  'VSurgery',
  'VTransfer',
];
function isNested(property) {
  let pl = property.split('.');
  if (pl.length > 0) {

    if (nestedProperty.includes(pl[0])) return true;
  }
  return false;
}

//#region addExtInfoForPreQuery
let sqlserver = require('mssql');
async function processDocInfo(doc, targetName, extInfo, request) {
  let v = doc[targetName]
  let sql = replaceSqlValue(extInfo['sql'], v)
  let result = await request.query(sql);
  return doc[targetName] + '(' + result.recordset[0][extInfo['targetCol']] + ')'
}
function replaceSqlValue(sql, value) {
  var rstr = new RegExp("#value#", "g");
  return sql.replace(rstr, value);
}
function getValidExtInfoList(doc, aggs, extInfos) {
  if (doc === undefined) return undefined;
  let r = {}
  for (const key in doc) {
    if (doc.hasOwnProperty(key)) {
      let ext = getExtInfo(key, aggs, extInfos)
      if (ext !== undefined)
        r[key] = ext
    }
  }
  return r
}
function getExtInfo(key, aggs, extInfos) {
  let r = undefined
  for (s of aggs) {
    for (t of s.terms) {
      if (key === t.name) {
        let i = extInfos.findIndex(v => v.baseValue === t.target)
        if (i > -1) {
          r = { info: extInfos[i], name: key }
        }
      }
    }
  }
  return r
}
async function processDocExtInfo(docs, aggs) {
  //console.log(JSON.stringify(docs));
  if (docs === undefined || docs.length === 0) return -1
  let store = getStore()
  let l = getValidExtInfoList(docs[0], aggs, store.extInfos)

  if (l.length == 0) return -1
  try {
    //let pool = await getConnection()
    var pool = new sqlserver.ConnectionPool(cfg.mapServerCfg);
    await pool.connect();
    let request = pool.request();
    request.multiple = true;

    for (d of docs) {
      for (const key in d) {
        if (d.hasOwnProperty(key) && l.hasOwnProperty(key)) {
          const ext = l[key];
          d[key] = await processDocInfo(d, ext.name, ext.info, request)
        }
      }
    }

  } catch (error) {
    console.log(error);
  } finally {
    //await doRelease(pool);
    await sqlserver.close();
  }
  return 1
}



//#endregion

//#region get es mapping info
module.exports.getMapping = async function (index) {
  let ss = {
    include_type_name: true,
  };
  ss['index'] = index;
  let strss = JSON.stringify(ss);
  let r = await getclient().indices.getMapping(ss, {
    ignore: [404],
    maxRetries: 3,
  });
  return r;
};
//#endregion

//#region get query dsl by condition
function getfiltercondition(con) {
  let rv = [];
  if (con.Operation === 'Eq' || con.Operation === '==') {
    let sv = con.Value;
    if (sv != undefined && sv !== '') {
      sv.split(',').forEach((v) => {
        let ro = {
          match_phrase: {
            [con.Target]: con.Value,
          },
        };
        rv.push(ro);
      });
    }
  }
  if (
    con.Operation === 'More' ||
    con.Operation === 'more' ||
    con.Operation === '>'
  ) {
    let ro = {};
    ro['range'] = {
      [con.Target]: {
        ['gt']: con.Value,
      },
    };
    rv.push(ro);
  }
  if (
    con.Operation === 'Less' ||
    con.Operation === 'less' ||
    con.Operation === '<'
  ) {
    let ro = {};
    ro['range'] = {
      [con.Target]: {
        ['lt']: con.Value,
      },
    };
    rv.push(ro);
  }
  if (
    con.Operation === 'MoreOrEqual' ||
    con.Operation === 'moreorequal' ||
    con.Operation === '>='
  ) {
    let ro = {};
    ro['range'] = {
      [con.Target]: {
        ['gte']: con.Value,
      },
    };
    rv.push(ro);
  }
  if (
    con.Operation === 'LessOrEqual' ||
    con.Operation === 'lessorequal' ||
    con.Operation === '<='
  ) {
    let ro = {};
    ro['range'] = {
      [con.Target]: {
        ['lte']: con.Value,
      },
    };
    rv.push(ro);
  }
  return rv;
}

function isText(con, mapping) {
  let rstr = new RegExp('\.', "g");
  //let xp = con.Target.replace(rstr, '.properties.');
  let xp = con.Target.replace(/\./g, ".properties.");
  let xl = xp.split('.');

  let value = xl.reduce((pv, cv) => {
    return pv[cv];
  }, mapping);
  if (value['type'] === 'text') return true;
  return false;
}
function findvalue(object, key) {
  for (let index = 0; index < Object.keys(object).length; index++) {
    const k = Object.keys(object)[index];
    if (k === key) return object[k];
    if (Object.prototype.toString.call(object[k]) === '[object Object]') {
      let r = findvalue(object[k], key)
      if (r !== undefined) return r
    }
  }

  return undefined
}
//from same nested target
function getNestedCondition(conlist, mapping, queryAsKeyword) {
  let r = []
  let map = {}; //key is nested object
  conlist.forEach(con => {
    let path = con.Target.split('.')[0];
    if (Object.keys(map).indexOf(path) > -1)
      map[path].push(con)
    else
      map[path] = [con]
  })
  let getss = (path) => {
    return {
      nested: {
        path: path,
        query: {
          bool: {
            must: [],
            should: [],
            must_not: [],
          },
        },
      },
    };
  }
  for (let index = 0; index < Object.keys(map).length; index++) {
    const key = Object.keys(map)[index];
    map[key].forEach((con) => {
      let ro = getcondition(con, mapping, queryAsKeyword);
      let ss = getss(key)
      if (
        con.Relation === 'And' ||
        con.Relation === 'and' ||
        con.Relation === 'AND'
      ) {
        ss['nested']['query']['bool']['must'].push(ro);
      }
      if (
        con.Relation === 'Or' ||
        con.Relation === 'or' ||
        con.Relation === 'OR'
      ) {
        ss['nested']['query']['bool']['should'].push(ro);
      }
      if (
        con.Relation === 'Not' ||
        con.Relation === 'not' ||
        con.Relation === 'NOT'
      ) {
        ss['nested']['query']['bool']['must_not'].push(ro);
      }
      r.push(ss)
    }
    );
  }

  return r;
}
//if QueryAsKeyword is undefined means always query as keyword
function isKeyword(QueryAsKeyword, name) {
  if (QueryAsKeyword === undefined)
    return true
  if (QueryAsKeyword.findIndex(v => v.toLowerCase() === name.toLowerCase()) > -1)
    return true;

  return false;
}
function getQueryField(fields, QueryAsKeyword, mapping) {
  if (fields === undefined || fields === '') return ''
  if (fields.includes(',')) {
    let fl = fields.split(',')
    let rf = []
    fl.forEach(f => {

      if (isKeyword(QueryAsKeyword, f))
        rf.push(f + '.keyword')
      else
        rf.push(f)
    })
    return rf.join(',')
  }

  if (isKeyword(QueryAsKeyword, fields))
    return fields + '.keyword'
  return fields
}
function getcondition(con, mapping, QueryAsKeyword) {

  if (IsAllField(con)) {
    rv = {
      multi_match: {
        query: con.Value,
        fields: getMultiFields(con),
        analyzer: cfg.analyzer,
      },
    };
    if (Isphrase(con)) {
      rv['multi_match']['type'] = 'phrase';
    }
    return rv;
  } else {
    if (Isphrase(con)) {
      //ignore operation
      return {
        multi_match: {
          query: con.Value,
          fields: con.Target,
          analyzer: cfg.analyzer,
          type: 'phrase',
        },
      };
    } else {
      if (con.Operation === 'Eq' || con.Operation === 'eq' || con.Operation === '==') {
        rv = {
          multi_match: {},
        };
        let f = con.Target
        if (isText(con, mapping)) {
          f = getQueryField(con.Target, QueryAsKeyword)
        }
        rv['multi_match']['query'] = con.Value;
        rv['multi_match']['fields'] = f;
        rv['multi_match']['analyzer'] = cfg.analyzer;
        return rv;
      }
      if (con.Operation === 'Empty' || con.Operation === 'empty') {

        rv = {
          term: {},
        };
        let field = con.Target
        if (isText(con, mapping))
          field = field + '.keyword'
        rv.term[field] = { value: '' };

        return rv;
      }
      if (con.Operation === 'NotEmpty' || con.Operation === 'Notempty' || con.Operation === 'notempty') {
        let field = con.Target
        if (isText(con, mapping))
          field = field + '.keyword'
        rv = {
          "script": {
            "script": "doc['" + field + "'].size()>0&&doc['" + field + "'].value.length()>0&&doc['" + field + "'].value.trim()!==''"
          }
        };

        return rv;
      }
      if (con.Operation === 'pre') {
        let rv = {
          wildcard: {},
        };
        let key = con.Target + '.keyword';
        rv['wildcard'][key] = con.Value + '*';
        return rv;
      }
      if (con.Operation === 'pst') {
        let rv = {
          wildcard: {},
        };
        let key = con.Target + '.keyword';
        rv['wildcard'][key] = '*' + con.Value;
        return rv;
      }
      if (con.Operation === 'wild') {
        let rv = {
          wildcard: {},
        };
        let key = con.Target + '.keyword';
        rv['wildcard'][key] = '*' + con.Value + '*';
        return rv;
      }
      if (con.Operation === 'Between' || con.Operation === 'between') {
        rv = {
          range: {},
        };

        vl = String(con.Value).split(',');
        if (vl.length > 1) {
          if (isText(con, mapping)) {
            rv['range'][con.Target + '.keyword'] = {
              gte: vl[0],
              lt: vl[1],
            };
          } else {
            rv['range'][con.Target] = {
              gte: vl[0],
              lt: vl[1],
            };
          }
        } else {
          if (con.AddtionalValue !== undefined && con.AddtionalValue !== '') {
            if (isText(con, mapping)) {
              rv['range'][con.Target + '.keyword'] = {
                gte: con.Value,
                lt: con.AddtionalValue,
              };
            } else {
              rv['range'][con.Target] = {
                gte: con.Value,
                lt: con.AddtionalValue,
              };
            }
          }
        }
        return rv;
      }
      if (
        con.Operation === 'More' ||
        con.Operation === 'more' ||
        con.Operation === '>'
      ) {
        rv = {
          range: {},
        };
        if (isText(con, mapping)) {
          rv['range'][con.Target + '.keyword'] = {
            gt: con.Value,
          };
        } else {
          rv['range'][con.Target] = {
            gt: con.Value,
          };
        }
        return rv;
      }
      if (
        con.Operation === 'Less' ||
        con.Operation === 'less' ||
        con.Operation === '<'
      ) {
        rv = {
          range: {},
        };
        if (isText(con, mapping)) {
          rv['range'][con.Target + '.keyword'] = {
            lt: con.Value,
          };
        } else {
          rv['range'][con.Target] = {
            lt: con.Value,
          };
        }
        return rv;
      }
      if (
        con.Operation === 'MoreOrEqual' ||
        con.Operation === 'moreorequal' ||
        con.Operation === '>='
      ) {
        rv = {
          range: {},
        };
        if (isText(con, mapping)) {
          rv['range'][con.Target + '.keyword'] = {
            gte: con.Value,
          };
        } else {
          rv['range'][con.Target] = {
            gte: con.Value,
          };
        }
        return rv;
      }
      if (
        con.Operation === 'LessOrEqual' ||
        con.Operation === 'lessorequal' ||
        con.Operation === '<='
      ) {
        rv = {
          range: {},
        };
        if (isText(con, mapping)) {
          rv['range'][con.Target + '.keyword'] = {
            lte: con.Value,
          };
        } else {
          rv['range'][con.Target] = {
            lte: con.Value,
          };
        }
        return rv;
      }
    }
  }

  return undefined;
}
getMultiFields = (con) => {
  if (
    con.Target === undefined ||
    con.Target === null ||
    con.Target.trim() === ''
  )
    return ['*'];
  let fs = con.Target.split(',');
  return fs;
};
IsAllField = (con) => {
  if (
    con.Target === undefined ||
    con.Target === null ||
    con.Target.trim() === ''
  )
    return true;
  let fs = con.Target.split(',');
  if (fs.length > 1) return true;
  return false;
};
Isphrase = (con) => {
  if (con.Value === undefined) return false;
  if (typeof con.Value !== 'string') return false;
  //if (con.Value.includes('%')) return false;
  if (con.Value.startsWith('"') && con.Value.endsWith('"')) return true;
  if (con.Value.startsWith("'") && con.Value.endsWith("'")) return true;
  return false;
};

function getQueryBaseObj(index, size, from) {
  let ss = {
    index: index,
    size: size,
    from: from,

    body: {
      timeout: cfg.timeout,
      min_score: cfg.minScore,
      query: {
        bool: {
          should: [],
          must: [],
          must_not: [],
          filter: {
            bool: {
              should: [],
              must: [],
              must_not: [],
            },
          },
        },
      },
    },
  };
  return ss;
}
//#endregion

//#region Set operate
// A∪B = { x | x ∈ A∨x ∈ B }
function union(source, target) {
  var unionSet = new Set();
  for (var i = 0; i < source.length; i++) {
    unionSet.add(source[i]);
  }
  for (i = 0; i < target.length; i++) {
    unionSet.add(target[i]);
  }
  return Array.from(unionSet.values());
}
// A∩B = { x | x ∈ A∧x ∈ B }
function intersection(source, target) {
  var intersectionSet = new Set();
  for (var i = 0; i < source.length; i++) {
    if (target.includes(source[i])) {
      intersectionSet.add(source[i]);
    }
  }
  return Array.from(intersectionSet.values());
}
// A-B = { x | x ∈ A ∧ x  B }
function except(source, target) {
  var differenceSet = new Set();
  for (var i = 0; i < source.length; i++) {
    if (!target.includes(source[i])) {
      differenceSet.add(source[i]);
    }
  }
  return Array.from(differenceSet.values());
}
//#endregion

//#region queryByID
function getQueryByIdBaseObject(index, ids, showCols, from, size) {
  let ss = {
    index: index,
    from: from,
    size: size,
    body: {
      timeout: cfg.timeout,
      query: {
        bool: {
          must: [
            {
              terms: {
                _id: ids,
              }
            }
          ]
        }

      },
    },
  };

  if (ids === undefined || ids === null || ids.length === 0)
    ss['body'] = {
      timeout: cfg.timeout,
    };
  if (showCols !== undefined && showCols.length > 0)
    ss['body']['_source'] = showCols;

  return ss;
}

async function getResultById(index, ids, showCols, from, size, sort) {
  let ss = getQueryByIdBaseObject(index, ids, showCols, from, size);

  if (sort != undefined && sort !== '') {
    let sobject = JSON.parse(sort);
    if (sobject['target'] != undefined && sobject['target'] != '') {
      so = {};
      so[sobject['target']] = sobject['order'];
      ss['body']['sort'] = [so];
    }
  }
  console.log(JSON.stringify(ss));
  try {
    let r = await getclient().search(ss, {
      ignore: [508],
      maxRetries: 3,
    });
    return r;
  } catch (err) {
    console.log('error in getResultById');
    console.log(err);
    throw err;
  }
}

function getIdByMridBaseObject(index, mrids, from, size) {
  let ss = {
    index: index,
    from: from,
    size: size,
    body: {
      timeout: cfg.timeout,
      query: {
        terms: {},
      },
    },
  };

  if (mrids === undefined || mrids === null || mrids.length === 0)
    ss['body'] = {
      timeout: cfg.timeout,
    };
  ss['body']['query']['terms'] = {
    'MRID.keyword': mrids,
  };
  //console.log(JSON.stringify(ss));
  return ss;
}

async function getIdByMrid(index, mrids, from, size) {
  let ss = getIdByMridBaseObject(index, mrids, from, size);
  //console.log(ss);
  try {
    let r = await getclient().search(ss, {
      ignore: [508],
      maxRetries: 3,
    });
    return r;
  } catch (err) {
    console.log(err);
    throw err;
  }
}
//#endregion

//#region row2col
function getResultColumn(propertyname) {
  if (cfg.columnmapping[propertyname] !== undefined) {
    return cfg.columnmapping[propertyname];
  }
  return propertyname;
}

function subKVObjToCol(obj, subProperty) {
  if (
    obj === undefined ||
    obj === null ||
    obj[subProperty] === undefined ||
    obj[subProperty] === null
  )
    return obj;
  obj[subProperty].forEach((element) => {

    for (let key in element) {
      obj[key.replace('.', '')] = element[key];
    }
  });
  delete obj[subProperty];
}

function subOrderObjToCol(
  obj,
  subProperty,
  orderColName,
  replaceShowName,
  preColName,
  limit,
  showCols
) {

  if (
    obj === undefined ||
    obj === null ||
    obj[subProperty] === undefined ||
    obj[subProperty] === null
  )
    return obj;
  let i = 0;

  if (orderColName !== undefined) {
    obj[subProperty] = obj[subProperty].sort((a, b) => a[orderColName] - b[orderColName])
  }
  obj[subProperty].forEach((element) => {
    i++;
    if (limit === undefined || limit <= 0 || i <= limit) {
      let order = undefined;
      if (orderColName !== undefined && !cfg.compactOrderedColumn) order = element[orderColName];
      if (order === undefined || order === null) order = i;

      for (let key of showCols) {
        if (replaceShowName) {
          let col = getResultColumn(key.split('.')[1]);
          obj[preColName + col + order] = element[key.split('.')[1]];
        } else {
          obj[key.split('.')[1] + order] = element[key.split('.')[1]];
        }
      }
    }
  });
  delete obj[subProperty];
}
//#endregion

//#region AsyncParallel

function isAsyncParallelCondition(con) {
  if (
    con != undefined &&
    con.Operation !== undefined &&
    con.Operation.trim().toLowerCase() === cfg.AsyncParallelOperator
  )
    return true;
  return false;
}

function isAsyncParallelField(cons) {
  let r = false;
  cons.forEach((c) => {
    if (isAsyncParallelCondition(c)) r = true;
  });
  return r;
}

function getAsyncParallelValues(con) {
  let rv = con.Value.split(',')
  let xv = []
  // var l = new RegExp('(', "g")
  // var r = new RegExp(')', "g")
  for (let index = 0; index < rv.length; index++) {
    const element = rv[index];
    //let v = element.replace(l, '').replace(r, '')
    xv.push(element)
  }
  //return con.Value.split(',');
  return { target: con.Target, values: xv }
}
function checkHits(hits, targetNum, sequence, strict) {

  if (hits.length < targetNum) return false
  let hited = []
  for (let index = 0; index < targetNum; index++) {
    let currentTarget = hits.findIndex(v => v.conditionIndex === index)
    if (currentTarget === -1)
      return false
    hited.push(hits[currentTarget].valueIndex)
    if (strict) {
      let currentSource = hits[currentTarget].valueIndex
      if (hits.findIndex(v => v.valueIndex == currentSource && v.conditionIndex !== index) > -1)
        return false
    }
  }

  if (sequence) {
    for (let index = 0; index < hited.length - 1; index++) {
      const current = hited[index];
      const next = hited[index + 1]
      if (current > next) {

        return false
      }
    }
  }
  return true
}
function isAsyncParallel(exts, conditionvalues, sequence, strict) {
  hit = []//
  // console.log(exts);
  // console.log(conditionvalues);
  for (let index = 0; index < exts.length; index++) {
    const ext = exts[index];
    ext['exts'].forEach(e => {
      for (let i = 0; i < conditionvalues.length; i++) {
        const conditionvalue = conditionvalues[i];

        if (e.hasOwnProperty(conditionvalue.target)) {
          const value = e[conditionvalue.target];
          let conditionIndex = conditionvalue.values.findIndex(v => v === value)
          if (conditionIndex > -1)
            hit.push({ valueIndex: index, conditionIndex: i })
        }
      }
    })
  }
  return checkHits(hit, conditionvalues.length, sequence, strict)

}

function getAsyncParallelField(cons, isAll = true) {
  f = [];
  if (isAll) f = ['MRID', 'AddmissionTime'];
  cons.forEach((c) => {
    if (isAsyncParallelCondition(c) && !f.includes(c.Target)) f.push(c.Target);
  });
  if (f.findIndex(v => v === 'MRID') === -1)
    f.push('MRID');
  return f;
}

function getValueList(element, target) {
  r = [];
  let fields = target.split('.');
  if (fields.length < 2) return [];
  let l = element[fields[0]];
  if (l === null || l === undefined)
    return r
  l.forEach((v) => {
    o = {};
    o[target] = v[fields[1]];
    r.push(o);
  });
  return r;
}

function getAsyncParallelResult(element, cons) {
  let o = {
    id: element['_id'],
    exts: [],
  };
  let targets = []
  cons.forEach((con) => {
    if (isAsyncParallelCondition(con) && targets.findIndex(v => v === con.Target) === -1) {
      targets.push(con.Target)
    }
  });
  targets.forEach(t => {
    l = getValueList(element['_source'], t);
    l.forEach((v) => o['exts'].push(v));
  })
  return o;
}
//#endregion

//#region search
//just return ids
async function commonsearch(index, cons, filters, isMridMode, sequence, strict, mapping, queryAsKeyword, showdsl = true) {
  let from = 0;

  let size = cfg.allSize;
  let ss = getQueryBaseObj(index, size, from);

  ss['index'] = index;
  let bo = getConditionBoolFilter(cons, filters, mapping, queryAsKeyword);
  if (!isMridMode && !isAsyncParallelField(cons)) ss['body']['_source'] = '';
  if (isMridMode) ss['body']['_source'] = 'MRID'; //every time reture mrid to prepare mrid mode query
  if (isAsyncParallelField(cons)) {
    let assource = getAsyncParallelField(cons)
    if (ss['body']['_source'] === undefined || ss['body']['_source'] === '')
      ss['body']['_source'] = assource;
    else {
      assource.push(ss['body']['_source'])
      ss['body']['_source'] = assource;
    }
  }
  ss.body.query.bool = bo;
  if (showdsl) {
    let strss = JSON.stringify(ss);
    console.log('finally commonsearch:' + strss);
  }
  try {
    let r = await getclient().search(ss, {
      ignore: [508],
      maxRetries: 3,
    });

    let ro = {
      total: 0,
      spend: 0,
      results: [],
    };

    if (r['hits']['hits'].length == 0) {
      ro['spend'] = r['took'];
      return ro;
    }

    if (isAsyncParallelField(cons)) {

      idl = [];
      mridl = [];
      mridsdic = {}; //key mrid,value {mrid,[{id,[ext]}]}

      r['hits']['hits'].forEach((element) => {
        let o = getAsyncParallelResult(element, cons);
        if (Object.keys(mridsdic).includes(element['_source']['MRID'])) {
          mridsdic[element['_source']['MRID']].push(o);
        } else {
          mridsdic[element['_source']['MRID']] = [o];
        }
      });
      // console.log('mridsdic');
      // console.log(JSON.stringify(mridsdic));
      let asynchronousc = Array.from(
        cons.filter((t) => isAsyncParallelCondition(t))
      );
      let checkValues = [];
      asynchronousc.forEach((con) => {
        let values = getAsyncParallelValues(con);
        checkValues.push(values)
      })

      for (key in mridsdic) {
        let termvalue = mridsdic[key];

        if (
          termvalue.length >= checkValues.length &&
          isAsyncParallel(termvalue, checkValues, sequence, strict)
        ) {

          if (isMridMode) mridl.push(key);
          else {
            termvalue.forEach(term => {
              idl.push(term['id']);
            })

          }
        }
      }

      if (isMridMode) {
        r = await getIdByMrid(index, mridl, from, size);
        r['hits']['hits'].forEach((element) => {
          ro['results'].push(element['_id']);
        });
      } else {
        idl.forEach((element) => {
          ro['results'].push(element);
        });
      }
    }
    else {
      if (isMridMode) {
        mrids = [];
        r['hits']['hits'].forEach((element) => {
          if (!mrids.includes(element['_source']['MRID']))
            mrids.push(element['_source']['MRID']);
        });
        r = await getIdByMrid(index, mrids, from, size);
      }

      r['hits']['hits'].forEach((element) => {
        ro['results'].push(element['_id']);
      });
    }
    ro['total'] = r['hits']['total']['value'];
    ro['spend'] = r['took'];
    return ro;
  } catch (err) {
    console.log(err);
    throw err;
  }
}
function getQueryAsKeyword(analyse = false) {
  if (analyse)
    return getStore().QueryAsKeyword
  if (!getStore().defaultKeyword)
    return getStore().QueryAsKeyword
  return undefined
}
async function getsearchIds(index, congroups, isMridMode, sequence, strict) {
  let indexmapping = await getclient().indices.getMapping({
    index: index,
  });

  let mapping = indexmapping[index]['mappings']['properties'];

  let queryAsKeyword = getQueryAsKeyword()

  results = [];
  let cgs = congroups.filter((v) => v.conditions.length > 0);//remove empty condition group
  for (let i = 0; i < cgs.length; i++) {
    let g = cgs[i];
    let r = await commonsearch(
      index,
      g.conditions,
      undefined,
      isMridMode,
      sequence, strict,
      mapping,
      queryAsKeyword
    );

    if (i === 0) {
      results = r['results']; //ignore group relation
    } else {
      if (g.relation.trim().toLowerCase() === 'intercect') {
        results = intersection(results, r['results']);
      }
      if (g.relation.trim().toLowerCase() === 'except')
        results = except(results, r['results']);
      if (g.relation.trim().toLowerCase() === 'union')
        results = union(results, r['results']);
    }
  }
  return results;
}
function addMissionCondition(showCols) {
  let r = []
  let l = getStore().OrderNestedDoc
  showCols.forEach(key => {
    l.forEach(o => {
      if (key.startsWith(o.doc) && r.findIndex(con => con === o.doc + '.' + o.orderProperty) === -1)
        r.push(o.doc + '.' + o.orderProperty)
      //r.push(cfg.tempPropertPre + o.doc + '.' + o.orderProperty)
    })
  })
  return r
}
module.exports.conditionsearch = async function conditionsearch(
  index,
  congroups,
  showCols,
  from,
  size,
  sort,
  parameter
) {
  let isMridMode = false
  if (parameter != undefined && parameter['isMridMode'] != undefined)
    isMridMode = parameter['isMridMode']

  let sequence = false
  if (parameter != undefined && parameter['sequence'] != undefined)
    sequence = parameter['sequence']
  let strict = false
  if (parameter != undefined && parameter['strict'] != undefined)
    strict = parameter['strict']
  if (from === undefined) from = 0;
  if (size === undefined) size = 100;
  startTime = new Date();
  try {
    let ids = await getsearchIds(index, congroups, isMridMode, sequence, strict);
    if (ids.length == 0) {
      return {
        total: 0,
        spend: new Date() - startTime,
        results: [],
        columns: [],
      };
    }
    console.log('get Id finished:' + ids.length);
    console.log(JSON.stringify(ids));
    let targetshowCols = undefined
    let missingShowCols = addMissionCondition(showCols);
    //console.log('missingShowCols is ' + missingShowCols);
    if (showCols !== undefined) {
      targetshowCols = [...showCols, ...missingShowCols]
    }

    let r = await getResultById(index, ids, targetshowCols, from, size, sort);

    //processhighlight(r)

    let ro = {
      total: ids.length,
      spend: new Date() - startTime,
      results: [],
      columns: [],
    };
    ids = null
    let mridList = undefined;

    if (parameter != undefined && parameter['distinctMrid'] != undefined && parameter['distinctMrid'])
      mridList = []
    let letSubShowCols = {}
    getStore().OrderNestedDoc.forEach(oc => {
      letSubShowCols[oc.doc] = showCols.filter(v => v.startsWith(oc.doc))
    })

    r['hits']['hits'].forEach((element) => {
      let obj = element['_source'];
      if (mridList === undefined || obj['MRID'] === undefined || mridList.indexOf(obj['MRID']) < 0) {
        if (mridList !== undefined && obj['MRID'] !== undefined)
          mridList.push(obj['MRID']);
        if (cfg.resultFlatMode) {
          if (cfg.resetColumnName) {
            for (let key of showCols) {
              if (
                !key.startsWith('Doctor') &&
                !key.startsWith('VFee') &&
                !key.startsWith('Diagnosis') &&
                !key.startsWith('VAllergyDiagnosis') &&
                !key.startsWith('VClinicDiagnosis') &&
                !key.startsWith('VContact') &&
                !key.startsWith('VICU') &&
                !key.startsWith('VOperate') &&
                !key.startsWith('VSurgery') &&
                !key.startsWith('VTransfer')
              ) {
                let col = getResultColumn(key);
                obj[col] = obj[key];
                delete obj[key];
              }
            }
          }
          subKVObjToCol(obj, 'Doctor');
          subKVObjToCol(obj, 'VFee');
          getStore().OrderNestedDoc.forEach(oc => {
            if (letSubShowCols[oc.doc] !== undefined && letSubShowCols[oc.doc] !== null && letSubShowCols[oc.doc].length > 0) {
              let limit = 0;
              if (parameter !== undefined || parameter['limit'] !== undefined) {
                if (parameter['limit'][oc.doc] !== undefined)
                  limit = parameter['limit'][oc.doc]
              }
              subOrderObjToCol(obj, oc.doc, oc.orderProperty, cfg.resetColumnName, oc.name, limit, letSubShowCols[oc.doc])
            }
          })
          if (showCols !== undefined) {
            let removeProperty = []
            let missingShow = []
            missingShowCols.forEach(s => {
              let ts = s
              if (s.includes('.'))
                ts = s.split('.')[1]
              let t = cfg.columnmapping[ts]
              if (t !== undefined)
                missingShow.push(t)
            })
            for (const key in obj) {
              missingShow.forEach(mkey => {
                if (isSameShowColumnNumber(mkey, key))
                  removeProperty.push(key)
              })
            }
            // console.log(missingShow);
            // console.log(removeProperty);
            removeProperty.forEach(key => {
              delete obj[key]
            })
            removeProperty = null
          }
        }
        if (obj !== undefined) {
          for (let propertyIndex = 0; propertyIndex < Object.keys(obj).length; propertyIndex++) {
            const property = Object.keys(obj)[propertyIndex];
            if (!ro['columns'].includes(property)) ro['columns'].splice(propertyIndex, 0, property);
          }
          // Object.keys(obj).forEach((c) => {
          //   if (!ro['columns'].includes(c)) ro['columns'].push(c);
          // });
        }
        ro['results'].push(obj);
      }
    });
    mridList = null
    r = null
    return ro;
  } catch (err) {
    console.log(err);
    throw err;
  }
};
function isSameShowColumnNumber(source, target) {
  let regPos = new RegExp(source + '\d*')
  return regPos.test(target)
}

module.exports.search = async function search(
  index,
  congroups,
  filters,
  from,
  size,
  sort,
  hightlightpretag,
  hightlightposttag
) {
  if (from === undefined) from = 0;
  if (size === undefined) size = 100;
  let ss = getQueryBaseObj(index, size, from);

  ss['index'] = index;
  if (hightlightpretag != undefined && hightlightposttag != undefined) {
    ss['body']['highlight'] = {
      pre_tags: hightlightpretag,
      post_tags: hightlightposttag,
      require_field_match: false,
      fields: {
        '*': {},
      },
    };
  }
  if (sort != undefined && sort !== '') {
    let sobject = JSON.parse(sort);
    if (sobject['target'] != undefined && sobject['target'] != '') {
      so = {};
      so[sobject['target']] = sobject['order'];
      ss['body']['sort'] = [so];
    }
  }
  let indexmapping = await getclient().indices.getMapping({
    index: index,
  });
  let mapping = indexmapping[index]['mappings']['properties'];
  let qAK = getQueryAsKeyword(true)
  congroups.forEach((g) => {
    let bo = getConditionBoolFilter(g.conditions, filters, mapping, qAK);
    if (g.relation.trim().toLowerCase() === 'intercect')
      ss.body.query.bool.must.push({
        bool: bo,
      });
    if (g.relation.trim().toLowerCase() === 'except')
      ss.body.query.bool.must_not.push({
        bool: bo,
      });
    if (g.relation.trim().toLowerCase() === 'union')
      ss.body.query.bool.should.push({
        bool: bo,
      });
  });

  let strss = JSON.stringify(ss);
  console.log('finally search:' + strss);
  try {
    let r = await getclient().search(ss, {
      ignore: [508],
      maxRetries: 3,
    });
    processhighlight(r);
    let ro = {
      total: 0,
      spend: 0,
      results: [],
    };

    r['hits']['hits'].forEach((element) => {
      ro['results'].push(element['_source']);
    });

    ro['total'] = r['hits']['total']['value'];
    ro['spend'] = r['took'];
    return ro;
  } catch (err) {
    console.log(err);
    throw err;
  }
};

//simplesearch just for cons search without condition group
module.exports.simplesearch = async function simplesearch(
  index,
  cons,
  filters,
  from,
  size,
  sort
) {
  if (from === undefined) from = 0;
  if (size === undefined) size = 100;

  let ss = getQueryBaseObj(index, size, from);

  ss['index'] = index;

  if (cfg.hightlightpretag != undefined && cfg.hightlightposttag != undefined) {
    ss['body']['highlight'] = {
      pre_tags: cfg.hightlightpretag,
      post_tags: cfg.hightlightposttag,

      require_field_match: false,
      fields: {
        '*': {},
      },
    };
  }
  if (sort != undefined && sort !== '') {
    let sobject = JSON.parse(sort);
    if (sobject['target'] != undefined && sobject['target'] != '') {
      so = {};
      so[sobject['target']] = sobject['order'];
      ss['body']['sort'] = [so];
    }
  }
  let indexmapping = await getclient().indices.getMapping({
    index: index,
  });
  let mapping = indexmapping[index]['mappings']['properties'];
  let qAK = getQueryAsKeyword()

  ss.body.query['bool'] = getConditionBoolFilter(cons, filters, mapping, qAK);

  let strss = JSON.stringify(ss);
  console.log('finally simplesearch:' + strss);
  try {
    let r = await getclient().search(ss, {
      ignore: [508],
      maxRetries: 3,
    });
    processhighlight(r);
    let ro = {
      results: [],
      total: 0,
      spend: 0
    };

    r['hits']['hits'].forEach((element) => {
      ro['results'].push(element['_source']);
    });

    ro['total'] = r['hits']['total']['value'];
    ro['spend'] = r['took'];
    return ro;
  } catch (err) {
    console.log(err);
    throw err;
  }
};

function processhighlight(r) {

  if (r['hits']['hits'] != undefined && Array.isArray(r['hits']['hits'])) {

    let data = Array.from(r['hits']['hits']);
    data.forEach((element) => {
      console.log(element['highlight']);
      if (element['highlight'] != undefined) {
        for (const skey in element['highlight']) {
          let key = skey
          if (skey.endsWith('.keyword')) {
            key = skey.substring(0, skey.length - 8)
          }
          if (!key.includes('.'))
            if (element['_source'].hasOwnProperty(key)) {
              if (Array.isArray(element['highlight'][key])) {
                element['_source'][key] = element['highlight'][key][0];
              } else {
                element['_source'][key] = element['highlight'][key];
              }
            }
            else {
              kl = key.split('.');
              if (element['_source'].hasOwnProperty(kl[0])) {
                let t = element['highlight'][kl[0]];
                if (Array.isArray(t)) {
                  console.log('array');
                  element['_source'][kl[0]] = element['highlight'][0];
                } else {
                  element['_source'][kl[0]] = element['highlight'][0];
                }
              }
            }
        }
        delete element['highlight'];
      }
    });

    r['hits']['hits'] = data;
    //console.log(JSON.stringify(data));
  }
}
//#endregion

//#region modify es data
module.exports.getES = function getES(searchInfo, callback) {
  const esClient = new elasticsearch.Client({
    host: cfg.host,
    log: 'error',
  });
  esClient.search(searchInfo).then(
    function (re) {
      callback(re.hits.hits);
    },
    function (err) {
      console.trace(err.message);
    }
  );
};

module.exports.CreateIndex = function CreateIndex(indexName) {
  getclient()
    .create(searchInfo)
    .then(
      function (re) {
        callback(re.hits.hits);
      },
      function (err) {
        console.trace(err.message);
      }
    );
};

module.exports.AddDocument = async function (indexName, id, document) {
  let ss = {};
  ss['index'] = indexName;
  ss['id'] = id;
  ss['body'] = document;

  let r = await getclient().create(ss, {
    ignore: [508],
    maxRetries: 3,
  });
  return r;
};
module.exports.addCounter = async function (index, id) {
  let ss = {};
  ss['index'] = index;
  ss['id'] = id;
  ss['body'] = {
    script: {
      source:
        'if(ctx._source.hits==null)ctx._source.hits=1;else ctx._source.hits += 1;',
    },
  };
  const response = await getclient().update(ss);
  return response;
};
module.exports.modifyDocument = async function (index, id, doc) {
  let ss = {};
  ss['index'] = index;

  ss['id'] = id;
  ss['body'] = doc;
  await getclient().update(ss);
};
module.exports.deleteDocument = async function (index, id) {
  let ss = {};
  ss['index'] = index;
  ss['id'] = id;
  let r = await getclient().delete(ss, {
    ignore: [509],
    maxRetries: 3,
  });
  return r;
};
module.exports.getDocumentById = async function (index, id) {
  let ss = {};
  ss['index'] = index;
  ss['id'] = id;
  try {
    let r = await getclient().get(ss);
    return r;
  } catch (err) {
    console.log('err:');
    console.log(err);
    return '';
  }
};

//#endregion

function processInAndNotIn(cons) {
  if (cons === undefined) return undefined;
  rcons = cons.filter((v) => (v.Operation !== 'in' && v.Operation !== cfg.AsyncParallelOperator));
  return rcons;
}
function isValidQueryCondition(con) {
  if (con['Operation'] === 'Empty' || con['Operation'] === 'empty' ||
    con.Operation === 'NotEmpty' || con.Operation === 'Notempty' || con.Operation === 'notempty') return true;
  if (
    con['Value'] !== undefined &&
    con['Value'] !== null &&
    con['Value'] !== '')
    return true;
  return false;
}
function getConditionBoolFilter(cons, filters, mapping, queryAsKeyword) {

  let bo = {

    should: [],
    must: [],
    must_not: [],
    filter: {
      bool: {
        should: [],
        must: [],
        must_not: [],
      },
    },
  };
  xcons = processInAndNotIn(cons);

  let ac = Array.from(
    xcons.filter(
      (t) => (t.Relation.toLowerCase() === 'and' || t.Relation.toLowerCase() === 'And' || t.Relation.toLowerCase() === 'AND')
    )
  );
  let oc = Array.from(
    xcons.filter(
      (t) => (t.Relation.toLowerCase() === 'or' || t.Relation.toLowerCase() === 'Or' || t.Relation.toLowerCase() === 'OR')
    )
  );
  let nc = Array.from(
    xcons.filter(
      (t) => (t.Relation.toLowerCase() === 'not' || t.Relation.toLowerCase() === 'Not' || t.Relation.toLowerCase() === 'NOT')
    )
  );

  let sac = ac.filter((v) => !isNested(v.Target));

  sac.forEach((element) => {
    if (isValidQueryCondition(element)) {
      let qs = getcondition(element, mapping, queryAsKeyword);
      if (qs !== undefined) bo.must.push(qs);
    }
  });

  let nestedac = ac.filter((v) => isNested(v.Target) && isValidQueryCondition(v));

  let qs = getNestedCondition(nestedac, mapping, queryAsKeyword);

  if (qs !== undefined && qs.length > 0) bo.must.push(...qs);

  let soc = oc.filter((v) => !isNested(v.Target));

  soc.forEach((element) => {

    if (
      isValidQueryCondition(element)
    ) {
      let qs = getcondition(element, mapping, queryAsKeyword);
      if (qs !== undefined) bo.should.push(qs);
    }
  });
  let nestedoc = oc.filter((v) => isNested(v.Target) && isValidQueryCondition(v));

  qs = getNestedCondition(nestedoc, mapping, queryAsKeyword);
  if (qs !== undefined && qs.length > 0) bo.should.push(...qs);

  let snc = nc.filter((v) => !isNested(v.Target));

  snc.forEach((element) => {
    if (
      isValidQueryCondition(element)
    ) {
      let qs = getcondition(element, mapping, queryAsKeyword);
      if (qs !== undefined && qs.length > 0) bo.must_not.push(...qs);
    }
  });
  let nestednc = nc.filter((v) => isNested(v.Target) && isValidQueryCondition(v));

  qs = getNestedCondition(nestednc, mapping, queryAsKeyword);
  if (qs !== undefined) bo.must_not.push(...qs);

  if (filters != undefined) {
    filterconditions = processInAndNotIn(filters);

    let fac = Array.from(
      filterconditions.filter((t) => t.Relation.toLowerCase() === 'and')
    );
    let foc = Array.from(
      filterconditions.filter((t) => t.Relation.toLowerCase() === 'or')
    );
    let fnc = Array.from(
      filterconditions.filter((t) => t.Relation.toLowerCase() === 'not')
    );
    fac.forEach((element) => {
      if (
        isValidQueryCondition(element)
      ) {
        let r = getfiltercondition(element, mapping);
        r.forEach((v) => bo.filter.bool.must.push(v));
      }
    });
    foc.forEach((element) => {
      if (
        isValidQueryCondition(element)
      ) {
        let r = getfiltercondition(element, mapping);
        r.forEach((v) => bo.filter.bool.should.push(v));
      }
    });
    fnc.forEach((element) => {
      if (
        isValidQueryCondition(element)
      ) {
        let r = getfiltercondition(element, mapping);
        r.forEach((v) => bo.filter.bool.must_not.push(v));
      }
    });
  }
  if (bo.should != undefined && bo.should.length > 0)
    bo['minimum_should_match'] = 1

  let ins = cons.filter((v) => v.Operation === 'in' || v.Operation === cfg.AsyncParallelOperator);

  let simplein = []
  let nestedin = []
  ins.forEach((c) => {
    let vl = c.Value.split(',');
    simplein = []
    nestedin = []
    vl.forEach((v) => {
      let cc = {
        Relation: c.Relation,
        Value: v,
        Target: c.Target,
        Parameter: c.Parameter,
        Show: c.Show,
        Operation: '==',
      };
      if (isNested(c.Target) && isValidQueryCondition(c))
        nestedin.push(cc)
      if (!isNested(c.Target) && isValidQueryCondition(c))
        simplein.push(cc)
    });
    let nestedinCons = getNestedCondition(nestedin, mapping, queryAsKeyword)
    let simpleinCons = []
    simplein.forEach(scon => {
      simpleinCons.push(getcondition(scon, mapping, queryAsKeyword))
    })

    let co = {
      bool:
      {
        should: [],
        minimum_should_match: 1
      }
    }
    nestedinCons.forEach(v => {
      co.bool.should.push(v)

    })
    simpleinCons.forEach(v => {
      co.bool.should.push(v)

    })
    if (c.Relation.toLowerCase() === 'and')
      bo.must.push(co)
    if (c.Relation.toLowerCase() === 'or')
      bo.should.push(co)
    if (c.Relation.toLowerCase() === 'not')
      bo.must_not.push(co)
  })
  return bo;
}

function getMissing(target, mapping) {
  if (target == undefined || target == null || target == null) {
    return undefined;
  }
  let tt = mapping[target];
  if (target.indexOf('.') > -1) {
    //only for 2 hierarchy
    tt = mapping[target.split('.')[0]]['properties'][target.split('.')[1]];
  }

  if (tt === undefined) return undefined;
  if (
    tt['type'] === 'text' ||
    tt['type'] === 'keyword' ||
    tt['type'] === 'string'
  ) {
    return 'N/A';
  }
  return 0;
}

function getTarget(target, mapping) {
  if (target == undefined || target == null || target == null) {
    return undefined;
  }

  let tt = mapping[target];
  if (target.indexOf('.') > -1) {
    //only for 2 hierarchy
    tt = mapping[target.split('.')[0]]['properties'][target.split('.')[1]];
  }
  if (tt === undefined) return target + '.keyword';
  if (tt['type'] === 'text') {
    let f = tt['fields'];
    if (f['keyword'] != undefined && f['keyword'] != null)
      return target + '.keyword';
  }
  return target;
}

//#region aggrigate

function getAggrigateObjectDetail(statictic) {
  if (statictic.statistictype === '联合分组计数')
    return {
      terms: {
        field: statictic.target + '.keyword',
        size: cfg.aggSize
      }
    }
  if (statictic.statistictype === '联合分组前缀') {
    let v = statictic['parameter']
    if (v === undefined || v === '') {
      v = cfg.defaultPreWordCound
    }
    return {
      terms: {
        script: {
          source:
            "def v ='No value';if(doc['" +
            statictic.target + '.keyword' +
            "'].size()>0) v=doc['" +
            statictic.target + '.keyword' +
            "'].value;def r = v;if(v.length()>" +
            v +
            ')r=v.substring(0,' +
            v +
            ');return r; ',
        },
        size: cfg.aggSize,
      }
    }
  }
  if (statictic.statistictype === '联合分组后缀') {
    let v = statictic['parameter']
    if (v === undefined || v === '') {
      v = cfg.defaultPostWordCound
    }
    return {
      terms: {
        script: {
          source:
            "def v ='No value';if(doc['" +
            statictic.target + '.keyword' +
            "'].size()>0) v=doc['" +
            statictic.target + '.keyword' +
            "'].value;def r = v;if(v.length()>" +
            v +
            ')r=v.substring(v.length()-1-' +
            v +
            ',(v.length()-1));return r ',
        },
        size: cfg.aggSize,
      }
    }
  }
  if (statictic.statistictype === '联合分组平均')
    return {
      avg: {
        field: statictic.target
      }
    }
  if (statictic.statistictype === '联合分组求和')
    return {
      sum: {
        field: statictic.target
      }
    }
  if (statictic.statistictype === '联合分组最大')
    return {
      max: {
        field: statictic.target
      }
    }
  if (statictic.statistictype === '联合分组最小')
    return {
      min: {
        field: statictic.target
      }
    }
  if (statictic.statistictype === '联合分组区间') {
    let pl = statictic.parameter.split(',')
    let r = {
      range: {
        field: statictic.target,
        keyed: true,
        ranges: []

      }
    }
    if (pl.length > 0) {
      if (pl.length === 1) {
        r.range.ranges.push({
          to: pl[0]
        })
        r.range.ranges.push({
          from: pl[0]
        })
      }

      if (pl.length > 1) {
        r.range.ranges.push({
          to: pl[0]
        })
        for (let index = 0; index < pl.length - 1; index++) {
          const element = pl[index];
          r.range.ranges.push({
            from: element,
            to: pl[index + 1]
          })
        }
        r.range.ranges.push({
          from: pl[pl.length - 1]
        })
      }

    }
    return r
  }
}
function getAggrigateObject(statistic) {
  let n = statistic.name
  let nestedn = 'nested' + n
  let nestedpath = undefined
  if (statistic.target.includes('.') && isNested(statistic.target))
    nestedpath = statistic.target.split('.')[0]
  let nestedobject = {

    "reverse_nested": {
    },
    "aggs": {
    }

  }
  let so = {
    aggs: {

    }
  }
  nestedobject.aggs[n] = getAggrigateObjectDetail(statistic)
  if (nestedpath !== undefined)
    nestedobject.reverse_nested['path'] = nestedpath
  so.aggs[nestedn] = nestedobject
  return so
}
function getAggrigateObjects(statistics) {
  let br = []
  let er = []
  let bn = []
  let groups = statistics.filter(v => v.statistictype == '联合分组计数'
    || v.statistictype == '联合分组区间' || v.statistictype == '联合分组前缀'
    || v.statistictype == '联合分组后缀')
  let exts = statistics.filter(v => v.statistictype !== '联合分组计数'
    && v.statistictype !== '联合分组区间' && v.statistictype !== '联合分组前缀'
    && v.statistictype !== '联合分组后缀')

  groups.forEach(s => {
    br.push(getAggrigateObject(s))
    bn.push(s.name)
  })
  exts.forEach(s => {
    er.push(getAggrigateObject(s))
  })
  return [br, er, bn]
}
function getFinalAggrigateObject(br, er, bn, insideAggs, insideAggsNames) {

  let eo = {
    aggs: {}
  }
  if (er !== undefined && er.length > 0) {
    er.forEach(e => {
      for (const iterator in e['aggs']) {
        eo.aggs[iterator] = e['aggs'][iterator]
      }
    })

    let x = findvalue(br[br.length - 1], bn[br.length - 1])
    x['aggs'] = eo['aggs']
    for (let index = 0; index < insideAggs.length; index++) {
      const element = insideAggs[index];
      x['aggs'][insideAggsNames[index]] = element
    }

  }

  for (let index = br.length - 1; index > 0; index--) {
    const element = br[index];
    let targetObj = br[index - 1]
    let x = findvalue(targetObj, bn[index - 1])
    x['aggs'] = element['aggs']
  }

  return br[0]
}
function getAggrigateObjectByStatistics(statistics, insideAggs, insideAggsNames) {
  let r = getAggrigateObjects(statistics)
  return getFinalAggrigateObject(r[0], r[1], r[2], insideAggs, insideAggsNames)
}

function getExtAggObjFromArray(statistics, mapping) {
  if (
    statistics === null ||
    statistics === undefined ||
    statistics.length === 0
  )
    return null;
  let so = [];
  let sg = statistics.filter((s) => s.statistictype.startsWith('联合分组计数'));
  let gg = statistics.filter((s) => s.statistictype.startsWith('联合分组前缀') || s.statistictype.startsWith('联合分组后缀'));
  let rangeg = statistics.filter((s) => s.statistictype.startsWith('联合分组区间'));
  let avg = statistics.filter((s) => s.statistictype.startsWith('联合分组平均'));
  let sumg = statistics.filter((s) => s.statistictype.startsWith('联合分组求和'));
  let maxg = statistics.filter((s) => s.statistictype.startsWith('联合分组最大'));
  let ming = statistics.filter((s) => s.statistictype.startsWith('联合分组最小'));

  sg.forEach((s) => {
    let n = s['name']
    let o = {}
    o[n] = {
      value_count: {
        field: getTarget(s['target'], mapping)
      }
    }
    so.push(o);
  });

  gg.forEach((s) => {
    let n = s['name']
    let o = {}
    let aggObj = getAggrigateObjectDetail(s)
    if (aggObj['terms'] != undefined) {
      aggObj = aggObj['terms']
      if (aggObj['size'] != undefined)
        delete aggObj['size']
    }
    o[n] = {
      value_count: aggObj
    }
    so.push(o);
  });

  rangeg.forEach((s) => {
    let n = s['name']
    let o = {}
    let aggObj = getAggrigateObjectDetail(s)
    if (aggObj['terms'] != undefined) {
      aggObj = aggObj['terms']
      if (aggObj['size'] != undefined)
        delete aggObj['size']
    }
    o[n] = {
      value_count: aggObj
    }
    so.push(o);
  });

  avg.forEach((s) => {
    let n = s['name']
    let o = {}
    o[n] = {
      avg: {
        field: getTarget(s['target'], mapping)
      }
    }
    so.push(o);
  });
  sumg.forEach((s) => {
    let n = s['name']
    let o = {}
    o[n] = {
      sum: {
        field: getTarget(s['target'], mapping)
      }
    }
    so.push(o);
  });
  maxg.forEach((s) => {
    let n = s['name']
    let o = {}
    o[n] = {
      max: {
        field: getTarget(s['target'], mapping)
      }
    }
    so.push(o);
  });
  ming.forEach((s) => {
    let n = s['name']
    let o = {}
    o[n] = {
      min: {
        field: getTarget(s['target'], mapping)
      }
    }
    so.push(o);
  });

  return so;
}

function getZeroAggrigateResult() {
  let zero =
  {
    took: 0,
    timed_out: false,
    _shards: { total: 1, successful: 1, skipped: 0, failed: 0 },
    hits: {
      total: { value: 0, relation: 'eq' },
      max_score: null,
      hits: [],
    },
    aggregations: { 病案号: { value: 0 } },
  }
  return zero
}
//get all combined statistics
function getCombinedStatistics(agg) {
  let combinedStatistics = []
  for (let i = 0; i < agg['statistics'].length; i++) {
    let statictic = agg['statistics'][i]
    let combined = {
      conditions: statictic['conditions'],
      terms: [],
      caculateterms: []
    }
    if (statictic['caculateterms'] !== undefined)
      statictic['caculateterms'].forEach(c => combined.caculateterms.push(c))
    for (let j = 0; j < statictic['terms'].length; j++) {
      let element = statictic['terms'][j];
      if (isCombined(element))
        combined.terms.push(element);
    }
    if (combined.terms.length > 0)
      combinedStatistics.push(combined)
  }
  return combinedStatistics
}

function getUnCombinedStatistics(agg) {
  let uncombinedStatistics = []
  for (let i = 0; i < agg['statistics'].length; i++) {
    let statictic = agg['statistics'][i]
    let uncombined = {
      conditions: statictic['conditions'],
      terms: []
    }
    for (let j = 0; j < statictic['terms'].length; j++) {
      let element = statictic['terms'][j];
      if (!isCombined(element))
        uncombined.terms.push(element);
    }
    if (uncombined.terms.length > 0)
      uncombinedStatistics.push(uncombined)
  }
  return uncombinedStatistics
}

function getEmptyResultStatistics(agg) {
  let emptyResultStatistics = []
  for (let i = 0; i < agg['statistics'].length; i++) {
    let statictic = agg['statistics'][i]
    let er = {
      conditions: statictic['conditions'],
      terms: []
    }
    for (let j = 0; j < statictic['terms'].length; j++) {
      let element = statictic['terms'][j];
      if (!isCombined(element))
        er.terms.push(element);
    }
    if (er.terms.length > 0)
      emptyResultStatistics.push(er)
  }
  return emptyResultStatistics
}
module.exports.aggrigate = async function aggrigate(targetIndex, agg, parameter) {
  let result = {
    //totalhits: 0,
    spends: 0,
    statisticResult: [],
    //expression:agg
  };
  statistics = [];
  let isMridMode = false
  if (parameter != undefined && parameter['isMridMode'] != undefined)
    isMridMode = parameter['isMridMode']
  let sequence = false
  if (parameter != undefined && parameter['sequence'] != undefined)
    sequence = parameter['sequence']
  let strict = false
  if (parameter != undefined && parameter['strict'] != undefined)
    strict = parameter['strict']
  let ids = await getsearchIds(targetIndex, agg['conditionpipe'], isMridMode, sequence, strict);//query common conditions
  if (ids.length == 0) {
    let zeroValue = getZeroAggrigateResult();
    let currentSttistic = getEmptyResultStatistics(agg)

    currentSttistic.forEach(s => {
      s['terms'].forEach((element) => {
        zeroValue['aggregations'][element['name']] = {
          value: 0,
        };
      })
      let ro = processAggrigateResult(zeroValue, s['terms']);
      result.statisticResult.push(ro);
    });

    return result;
  }

  const combined = getCombinedStatistics(agg)
  const unCombined = getUnCombinedStatistics(agg)

  let combinedResult = {
    spends: 0,
    statisticResult: []
  }
  if (combined !== undefined && combined.length > 0)
    combinedResult = await aggrigateCombine(targetIndex, combined, ids)

  for (let i = 0; i < unCombined.length; i++) {
    let currentSttistic = unCombined[i]
    let r = await aggrigatestep(targetIndex, ids, currentSttistic['conditions'], currentSttistic['terms']);
    statistics = [...statistics, ...currentSttistic['terms']];
    let ro = processAggrigateResult(r, currentSttistic['terms']);
    result.spends += r.took;
    result.statisticResult.push(ro);
  }

  result['combined'] = []
  combinedResult.statisticResult.forEach(v => {
    result['combined'].push(v);
  })
  result.spends += (combinedResult).spends;
  return result;
};

//get all combined statistic results
async function aggrigateCombine(targetidnex, agg, ids) {

  let result = {
    //totalhits: 0,
    spends: 0,
    statisticResult: [],
    //expression:agg
  };
  statistics = [];

  let indexmapping = await getclient().indices.getMapping({
    index: targetidnex,
  });
  let mapping = indexmapping[targetidnex]['mappings']['properties'];
  let queryAsKeyword = getQueryAsKeyword()
  let showCols = statistics.map((v) => v['target']);
  let ss = getQueryByIdBaseObject(targetidnex, ids, showCols, 0, 0);

  let insideAggs = [];
  let insideNames = []
  //每组聚合必须来自于同一个复合文档
  for (let j = 1; j < agg.length; j++) {
    const conditions = agg[j]['conditions'];
    const statistics = agg[j]['terms'];
    if (statistics.length > 0) {
      let conObj = getConditionBoolFilter(conditions, undefined, mapping, queryAsKeyword)
      let esl = getExtAggObjFromArray(statistics, mapping)
      let aggObjectName = "externalGroupAgg" + j
      let inObjectName = "internalAgg" + j
      let aggObject = {
        "reverse_nested": {},
        "aggs": {
        }
      }

      if (isNested(statistics[0].target)) {
        aggObject.reverse_nested = {
          path: statistics[0].target.split('.')[0]
        }
      }
      aggObject.aggs[inObjectName] = {
        filter: {
          bool: conObj
        },
        aggs: {}
      }

      esl.forEach(s => {
        for (const iterator in s) {
          aggObject['aggs'][inObjectName]['aggs'][iterator] = s[iterator]
        }

        insideAggs.push(aggObject);
        insideNames.push(aggObjectName)
      })
    }
  }
  let ago = getAggrigateObjectByStatistics(agg[0]['terms'], insideAggs, insideNames)
  let basenested = "Diagnosis"
  let bagg = agg[0]['terms'].filter(v => v.statistictype === '联合分组计数' || v.statistictype === '联合分组区间'
    || v.statistictype === '联合分组前缀' || v.statistictype === '联合分组后')
  if (bagg !== undefined && bagg.length > 0 && isNested(bagg[0].target))
    basenested = bagg[0].target.split('.')[0]
  ss.body['aggs'] = {
    groupAggs: {
      "nested": {
        "path": basenested
      },
      "aggs": ago['aggs']
    }
  }

  console.log(JSON.stringify(ss));
  try {
    let r = await getclient().search(ss, {
      ignore: [508],
      maxRetries: 3,
    });
    console.log(JSON.stringify(r));
    let ro = processCombinedResult(r, agg);
    await processDocExtInfo(ro, agg)
    //console.log(JSON.stringify(ro));
    result.spends += r.took;
    ro.forEach(x => {
      result.statisticResult.push(x);
    })
    console.log(JSON.stringify(result));
    return result;
  } catch (err) {
    console.log(err);
    //log.logError(ctx, error, ms);
    logger(err);
    log.logError(err);

    fs.writeFileSync('.\\log\\errorfs.log', JSON.stringify(err));
    console.log('err in agg step');
    throw err;
  }

};
function getKeyValue(re, name) {

  if (re['key'] === undefined && re[name] === undefined)
    return re
  if (re['key'] === undefined) return re[name]
  let r = re['key']
  if (r[name] === undefined) return r
  return r[name]
}
function getKeyCount(re, name) {
  if (re['doc_count'] !== undefined) return re['doc_count']
  if (re['key'] === undefined && re[name] === undefined && re['doc_count'] === undefined)
    return re
  if (re['key'] === undefined && re[name] === undefined) return re['doc_count']
  if (re['key'] === undefined) return re[name]['doc_count']
  let r = re['key']
  if (r[name] === undefined) return r['doc_count']
  return r[name]['doc_count']
}
function getCombinedResult(r, termstatistics, i, othersstatistics, extSTatistic, cals) {

  let ol = []
  let buckets = findvalue(r, 'buckets')
  let name = termstatistics[i]['name']

  if (termstatistics.length === i + 1) {
    if (termstatistics[i].statistictype === '联合分组计数' ||
      termstatistics[i].statistictype === '联合分组前缀' ||
      termstatistics[i].statistictype === '联合分组后缀')
      buckets.forEach(re => {
        let o = {}
        o[name] = getKeyValue(re, name)
        o['value'] = getKeyCount(re, name)

        if (othersstatistics !== undefined) {
          othersstatistics.forEach(os => {
            let ov = findvalue(re, os['name'])
            if (ov !== undefined)
              o[os['name']] = ov['value']
          })
        }
        if (extSTatistic !== undefined) {
          extSTatistic.forEach(os => {
            let ov = findvalue(re, os['name'])
            if (ov !== undefined)
              o[os['name']] = ov['value']
          })
        }
        ol.push(o)
      })
    else
      for (const key in buckets) {
        if (buckets.hasOwnProperty(key)) {
          const element = buckets[key];
          let o = {}
          let okey = key.replace(/\./g, '*')
          o[okey] = element['doc_count']
          if (othersstatistics !== undefined) {
            othersstatistics.forEach(os => {
              let ov = findvalue(element, os['name'])
              if (ov !== undefined)
                o[os['name']] = ov['value']
            })
          }
          if (extSTatistic !== undefined) {
            extSTatistic.forEach(os => {
              let ov = findvalue(element, os['name'])

              if (ov !== undefined)
                o[os['name']] = ov['value']
            })
          }
          ol.push(o)
        }
      }
    buckets = {}
  }

  else {
    if (buckets.length === undefined) {
      for (const key in buckets) {
        const element = buckets[key];
        let l = getCombinedResult(element, termstatistics, i + 1, othersstatistics, extSTatistic, cals)
        l.forEach(o => {
          let okey = key.replace(/\./g, '*')
          o[okey] = element['doc_count']
          if (othersstatistics !== undefined) {
            othersstatistics.forEach(os => {
              let ov = findvalue(element, os['name'])
              if (ov !== undefined)
                o[os['name']] = ov['value']
            })
          }
          if (extSTatistic !== undefined) {
            extSTatistic.forEach(os => {
              let ov = findvalue(element, os['name'])
              if (ov !== undefined)
                o[os['name']] = ov['value']
            })
          }
          ol.push(o)
        })
      }
    }
    else {
      for (let index = 0; index < buckets.length; index++) {
        const element = buckets[index];
        let l = getCombinedResult(element, termstatistics, i + 1, othersstatistics, extSTatistic, cals)

        l.forEach(v => {
          if (v[name] === undefined)
            v[name] = getKeyValue(element, name)
          let newValue = JSON.parse(JSON.stringify(element))
          let nextName = termstatistics[i + 1]['name']
          let nextObjResult = findvalue(newValue, nextName)


          if (othersstatistics !== undefined)
            othersstatistics.forEach(os => {
              let ov = findvalue(newValue, os['name'])
              let nv = findvalue(nextObjResult, os['name'])
              if (ov !== undefined && nv === undefined)
                v[os['name']] = ov['value']
            })
          if (extSTatistic !== undefined)
            extSTatistic.forEach(os => {
              let ov = findvalue(newValue, os['name'])
              let nv = findvalue(nextObjResult, os['name'])
              if (ov !== undefined && nv === undefined)
                v[os['name']] = ov['value']
            })
          ol.push(v)
        })

      }
    }
  }

  if (cals.length > 0) {
    ol.forEach(o => {

      try {
        cals.forEach(c => {

          let name = c['name']
          let form = c['form']
          for (const iterator in o) {

            var rstr = new RegExp(iterator, "g");
            form = form.replace(rstr, o[iterator]);
          }
          //console.log(form);
          let value = eval(form);
          o[name] = value
        })
      }
      catch (err) {
        console.log(err);
        console.log(o)
      }
    })
  }
  return ol

}
function processCombinedResult(r, agg) {
  col = []
  let cals = []
  let baseStatistics = agg[0]['terms'].filter(v => v.statistictype === '联合分组计数'
    || v.statistictype === '联合分组区间' || v.statistictype === '联合分组前缀' || v.statistictype === '联合分组后缀'
  )
  let otherStatistics = agg[0]['terms'].filter(v => v.statistictype !== '联合分组计数'
    && v.statistictype !== '联合分组区间' && v.statistictype !== '联合分组前缀' && v.statistictype !== '联合分组后缀')
  let extSTatistic = []
  for (let index = 0; index < agg.length; index++) {
    if (agg[index]['caculateterms'] !== undefined)
      agg[index]['caculateterms'].forEach(c => cals.push(c))
  }
  for (let index = 1; index < agg.length; index++) {
    agg[index]['terms'].forEach(s => extSTatistic.push(s));

  }
  let rs = getCombinedResult(r, baseStatistics, 0, otherStatistics, extSTatistic, cals)

  //console.log(JSON.stringify(rs));
  return rs

}

async function aggrigatestep(index, ids, conditions, statistics) {
  let indexmapping = await getclient().indices.getMapping({
    index: index,
  });
  let mapping = indexmapping[index]['mappings']['properties'];
  let queryAsKeyword = getQueryAsKeyword()
  let showCols = statistics.map((v) => v['target']);
  let ss = getQueryByIdBaseObject(index, ids, showCols, 0, 0);
  let conObj = undefined
  if (conditions !== undefined && conditions.length > 0) {
    conObj = getConditionBoolFilter(conditions, undefined, mapping, queryAsKeyword)

    ss.body.query.bool.must.push({ bool: conObj })
  }
  ss.body['aggs'] = {};

  let sl = statistics.filter((s) => !s.statistictype.startsWith('联合分组'));
  sl.forEach((s) => {
    if (isNested(s.target)) {
      let nestedProperty = s.target.split('.')[0]
      let nestedName = 'nested' + s['name']
      if (s.statistictype === '分组计数') {
        ss.body.aggs[s['name']] = {
          nested: {
            path: nestedProperty
          },
          aggs: {

          }
        },

          ss.body.aggs[s['name']].aggs[nestedName] = {
            terms: {
              field: getTarget(s['target'], mapping),
              missing: getMissing(s['target'], mapping),
              size: cfg.aggSize,
            }
          }
      }
      if (s.statistictype === '区间计数') {
        let v = 10;
        if (
          s['parameter'] !== undefined &&
          s['parameter'] !== null &&
          s['parameter'].trim() !== ''
        )
          v = s['parameter'];
        ss.body.aggs[s['name']] = {
          nested: {
            path: nestedProperty
          },
          aggs: {

          }
        }
        ss.body.aggs[s['name']].aggs[nestedName] = {
          histogram: {
            field: getTarget(s['target'], mapping),
            interval: v,
          }
        }
      }
      if (s.statistictype === '去重计数') {
        ss.body.aggs[s['name']] = {
          nested: {
            path: nestedProperty
          },
          aggs: {

          }
        }

        ss.body.aggs[s['name']].aggs[nestedName] = {
          cardinality: {
            field: getTarget(s['target'], mapping),
            missing: getMissing(s['target'], mapping),
          }
        }
      }
      if (s.statistictype === '分布统计') {
        ss.body.aggs[s['name']] = {
          nested: {
            path: nestedProperty
          },
          aggs: {

          }
        }

        ss.body.aggs[s['name']].aggs[nestedName] = {
          percentiles: {
            field: getTarget(s['target'], mapping),
          }
        }
      }
      if (s.statistictype === '基本统计信息') {
        ss.body.aggs[s['name']] = {
          nested: {
            path: nestedProperty
          },
          aggs: {

          }
        }

        ss.body.aggs[s['name']].aggs[nestedName] = {
          stats: {
            field: getTarget(s['target'], mapping),
          }
        }
      }
      if (s.statistictype === '详细统计信息') {
        ss.body.aggs[s['name']] = {
          nested: {
            path: nestedProperty
          },
          aggs: {

          }
        }

        ss.body.aggs[s['name']].aggs[nestedName] = {
          extended_stats: {
            field: getTarget(s['target'], mapping),
          }
        }
      }
      if (s.statistictype === '结果计数') {
        ss.body.aggs[s['name']] = {
          nested: {
            path: nestedProperty
          },
          aggs: {

          }
        }

        ss.body.aggs[s['name']].aggs[nestedName] = {
          value_count: {
            field: getTarget(s['target'], mapping),
            missing: getMissing(s['target'], mapping),
          }
        }
      }
      if (s.statistictype === '前缀计数') {
        let v = 3;
        if (
          s['parameter'] !== undefined &&
          s['parameter'] !== null &&
          s['parameter'].trim() !== ''
        )
          v = s['parameter'];
        ss.body.aggs[s['name']] = {
          nested: {
            path: nestedProperty
          },
          aggs: {
          }
        }

        ss.body.aggs[s['name']].aggs[nestedName] = {
          terms: {
            script: {
              source:
                "def v ='No value';if(doc['" +
                getTarget(s['target'], mapping) +
                "'].size()>0) v=doc['" +
                getTarget(s['target'], mapping) +
                "'].value;def r = v;if(v.length()>" +
                v +
                ')r=v.substring(0,' +
                v +
                ');return r; ',
            },
            missing: getMissing(s['target'], mapping),
            size: cfg.aggSize,
          }
        }
      }
      if (s.statistictype === '后缀计数') {
        let v = 3;
        if (
          s['parameter'] !== undefined &&
          s['parameter'] !== null &&
          s['parameter'].trim() !== ''
        )
          v = s['parameter'];
        ss.body.aggs[s['name']] = {
          nested: {
            path: nestedProperty
          },
          aggs: {

          }
        }

        ss.body.aggs[s['name']].aggs[nestedName] = {
          terms: {
            script: {
              source:
                "def v ='No value';if(doc['" +
                getTarget(s['target'], mapping) +
                "'].size()>0) v=doc['" +
                getTarget(s['target'], mapping) +
                "'].value;def r = v;if(v.length()>" +
                v +
                ')r=v.substring(v.length()-1-' +
                v +
                ',(v.length()-1));return r ',
            },
            missing: getMissing(s['target'], mapping),
            size: cfg.aggSize,
          }
        }
      }
    } else {
      if (s.statistictype === '分组计数') {
        ss.body.aggs[s['name']] = {
          terms: {
            field: getTarget(s['target'], mapping),
            missing: getMissing(s['target'], mapping),
            size: cfg.aggSize,
          },
        };
      }
      if (s.statistictype === '区间计数') {
        let v = 10;
        if (
          s['parameter'] !== undefined &&
          s['parameter'] !== null &&
          s['parameter'].trim() !== ''
        )
          v = s['parameter'];
        ss.body.aggs[s['name']] = {
          histogram: {
            field: getTarget(s['target'], mapping),
            interval: v,
          },
        };
      }
      if (s.statistictype === '去重计数') {
        ss.body.aggs[s['name']] = {
          cardinality: {
            field: getTarget(s['target'], mapping),
            missing: getMissing(s['target'], mapping),
          },
        };
      }
      if (s.statistictype === '分布统计') {
        ss.body.aggs[s['name']] = {
          percentiles: {
            field: getTarget(s['target'], mapping),
          },
        };
      }
      if (s.statistictype === '基本统计信息') {
        ss.body.aggs[s['name']] = {
          stats: {
            field: getTarget(s['target'], mapping),
          },
        };
      }
      if (s.statistictype === '详细统计信息') {
        ss.body.aggs[s['name']] = {
          extended_stats: {
            field: getTarget(s['target'], mapping),
          },
        };
      }
      if (s.statistictype === '结果计数') {
        ss.body.aggs[s['name']] = {
          value_count: {
            field: getTarget(s['target'], mapping),
            missing: getMissing(s['target'], mapping),
          },
        };
      }
      if (s.statistictype === '前缀计数') {
        let v = 3;
        if (
          s['parameter'] !== undefined &&
          s['parameter'] !== null &&
          s['parameter'].trim() !== ''
        )
          v = s['parameter'];
        ss.body.aggs[s['name']] = {
          terms: {
            script: {
              source:
                "def v ='No value';if(doc['" +
                getTarget(s['target'], mapping) +
                "'].size()>0) v=doc['" +
                getTarget(s['target'], mapping) +
                "'].value;def r = v;if(v.length()>" +
                v +
                ')r=v.substring(0,' +
                v +
                ');return r ',
            },
            missing: getMissing(s['target'], mapping),
            size: cfg.aggSize,
          },
        };
      }
      if (s.statistictype === '后缀计数') {
        let v = 3;
        if (
          s['parameter'] !== undefined &&
          s['parameter'] !== null &&
          s['parameter'].trim() !== ''
        )
          v = s['parameter'];
        ss.body.aggs[s['name']] = {
          terms: {
            script: {
              source:
                "def v ='No value';if(doc['" +
                getTarget(s['target'], mapping) +
                "'].size()>0) v=doc['" +
                getTarget(s['target'], mapping) +
                "'].value;def r = v;if(v.length()>" +
                v +
                ')r=v.substring(v.length()-1-' +
                v +
                ',(v.length()-1));return r ',
            },
            missing: getMissing(s['target'], mapping),
            size: cfg.aggSize,
          },
        };
      }
    }
  });

  let strss = JSON.stringify(ss);
  console.log('finally aggrigatestep:' + strss);
  fs.appendFileSync('.\\info.log', JSON.stringify(ss));
  if (conObj !== undefined)
    fs.appendFileSync('.\\info.log', JSON.stringify(conObj));
  try {
    let r = await getclient().search(ss, {
      ignore: [508],
      maxRetries: 3,
    });

    return r;
  } catch (err) {
    console.log(err);
    //log.logError(ctx, error, ms);
    logger(err);
    log.logError(err);

    fs.writeFileSync('.\\log\\errorfs.log', JSON.stringify(err));
    console.log('err in agg step');
  }
}
function isCombined(statistic) {
  if (statistic !== undefined && statistic.statistictype !== undefined &&
    statistic.statistictype.startsWith('联合分组'))
    return true;
  return false;
}
function processAggrigateResult(r, statistics) {
  let ro = r.aggregations;
  result = {};

  let sg = statistics.filter((s) => isCombined(s));
  if (sg.length > 0) {
    let sss = getgroupstatistic(ro);
    let ss = sg.map((v) => v['name']).join(',');
    result[ss] = sss;
  }
  let sl = statistics.filter((s) => !isCombined(s));
  sl.forEach((s) => {
    let targetName = s.name
    if (isNested(s.target))
      targetName = 'nested' + s.name
    if (s.statistictype == '去重计数') {
      let sss = getValue(ro, targetName);
      result[s.name] = sss;
    }
    if (s.statistictype == '结果计数') {
      let sss = getValue(ro, targetName);
      result[s.name] = sss;
    }
    if (s.statistictype == '前缀计数') {
      let sss = getstatistic(ro, targetName);
      result[s.name] = sss;
    }
    if (s.statistictype == '后缀计数') {
      let sss = getstatistic(ro, targetName);
      result[s.name] = sss;
    }
    if (s.statistictype === '分组计数') {
      let sss = getstatistic(ro, targetName);
      result[s.name] = sss;
    }
    if (s.statistictype === '区间计数') {
      let sss = getbucketsvalue(ro, targetName);
      result[s.name] = sss;
    }
    if (s.statistictype === '分布统计') {
      let sss = getValues(ro, targetName);
      result[s.name] = sss;
    }
    if (s.statistictype === '基本统计信息') {
      let sss = getstatisticValue(ro, targetName);
      result[s.name] = sss;
    }
    if (s.statistictype === '详细统计信息') {
      let sss = getdeepstatisticValue(ro, targetName);
      result[s.name] = sss;
    }
  });
  return result;
}

function getdeepstatisticValue(ro, name) {
  let s = {};
  let rv = findvalue(ro, name)
  if (rv !== undefined)

    for (let k in rv) {
      if (typeof rv[k] != 'object') s[k] = rv[k].toString();
      else {
        for (let sk in rv[k])
          s[k + '.' + sk] = rv[k][sk].toString();
      }

    }
  return s;
}

function getstatisticValue(ro, name) {
  let s = {};
  let rv = findvalue(ro, name)
  if (rv !== undefined)

    for (let k in rv) {
      if (typeof rv[k] != 'object') s[k] = rv[k].toString();
      else {
        s[k] = rv[k];
      }

    }
  return s;
}

function getbucketsvalue(ro, name) {
  let r = {};
  let rv = findvalue(ro, name)
  if (rv !== undefined)
    for (let index = 0; index < rv['buckets'].length; index++) {
      let element = rv['buckets'][index];
      r[element['key']] = element['doc_count'];
    }
  return r;
}

function getValues(ro, name) {
  let rv = findvalue(ro, name)
  if (rv !== undefined)
    return rv['values'];

  return '0';
}

function getValue(ro, name) {
  let rv = findvalue(ro, name)
  if (rv !== undefined)
    return rv['value'];
  return '0';
}

function getGroupCombinedKey(obj) {
  let keys = [];
  for (const key in obj) {
    if (obj.hasOwnProperty(key)) {
      keys.push(obj[key].toString());
    }
  }
  return keys.join(',');
}

function getgroupstatistic(ro) {
  let rd = [];
  for (let index = 0; index < ro['groupAggs']['buckets'].length; index++) {
    let element = ro['groupAggs']['buckets'][index];
    let value = getGroupCombinedKey(element['key']) + ',' + element['doc_count']
    for (let keyIndex = 0; keyIndex < Object.keys(element).length; keyIndex++) {
      const skey = Object.keys(element)[keyIndex];
      if (skey != 'key' && skey != 'doc_count') {
        value += ',' + element[skey]['value']
      }
    }
    rd.push(value);
  }
  return rd;
}

function getstatistic(ro, name) {
  let rd = {};
  let rv = findvalue(ro, name)
  if (rv !== undefined) {
    let bs = rv['buckets'];
    bs.forEach((element) => {
      rd[element['key']] = element['doc_count'];
    });
  }
  return rd;
}

//#endregion
